/*
 * ﻿Copyright (C) 2011-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.aspectjutil.eventmanager;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Comparator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link Comparator} for avoiding duplicate preservation of an event listener method and 
 * deciding sort order among event listener methods in turn deciding execution order among them.
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
public class SubscriberMethodUnitComparator implements Comparator<SubscriberMethodUnit> {
	protected Logger logger = LoggerFactory.getLogger( this.getClass());
	
	int postModifierComparison( 
			SubscriberMethodUnit unit1, int unit1MethodModifier, 
			SubscriberMethodUnit unit2, int unit2MethodModifier) 
	{
		int timeStampComparisonResult = unit1.timeStamp.compareTo( unit2.timeStamp);
			if ( timeStampComparisonResult != 0) return timeStampComparisonResult;
		
		Class<?> unit1MethodClass = unit1.getMethodClass();
		Class<?> unit2MethodClass = unit2.getMethodClass();
			if ( !unit1MethodClass.equals( unit2MethodClass)) {
				if ( unit2MethodClass.isAssignableFrom( unit1MethodClass)) return -1;
				if ( unit1MethodClass.isAssignableFrom( unit2MethodClass)) return 1;
				
				int classNameComparisonResult 
				= unit1MethodClass.getName().compareTo( unit2MethodClass.getName());
					if ( classNameComparisonResult != 0) return classNameComparisonResult;
			}
		Method unit1Method = unit1.getMethod();
		Method unit2Method = unit2.getMethod();
			int methodNameComparisonResult 
			= unit1Method.toString().compareTo( unit2Method.toString());
				if ( logger.isDebugEnabled() && ( methodNameComparisonResult == 0)) {
					logger.debug( 
							String.format(
									"Comparison came out as identical for " 
									+ "next 2 SubscriberMethodUnit objects:" 
									+ "%n%1$c%2$s" + "%n%1$c%3$s",
									'\t',
									unit1.toString(), 
									unit2.toString())
							);
				}
			return methodNameComparisonResult;
	}
	
	/**
	 * Compare objects of {@link SubscriberMethodUnit} representing event listener method.
	 * Result of comparison of SubscriberMethodUnit objects are ordered in ascending by next priority:
	 * <ol> 
	 * <li>value of {@link SubscriberMethodUnit#turn} what is taken from either 
	 * {@link OnEvent#orderForEachEvent} or {@link PreEvent#orderForEachEvent} attribute<br />
	 * Smaller (positive) number has higher priority.
	 * </li>
	 * <li>synchronized method</li>
	 * <li>static visibility method</li>
	 * <li>public visibility method</li>
	 * <li>protected visibility method</li>
	 * <li>package level visibility method</li>
	 * <li>private visibility method</li>
	 * <li>{@link SubscriberMethodUnit#timeStamp}</li>
	 * <li>Class hierarchy</li>
	 * <li>Class name</li>
	 * <li>Method name</li>
	 * </ol>
	 * 
	 * @param unit1 <code>SubscriberMethodUnit</code> object for comparison
	 * @param unit2 <code>SubscriberMethodUnit</code> object for comparison
	 */
	@Override
	public int compare( SubscriberMethodUnit unit1, SubscriberMethodUnit unit2) {
		if ( unit1.equals( unit2)) return 0;
		
		if ( unit1.turn < unit2.turn) return -1;
		if ( unit1.turn > unit2.turn) return 1;
		
		// For comparison, instead may use actual modifier int value: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1
		int unit1MethodModifier = unit1.getMethod().getModifiers();
		int unit2MethodModifier = unit2.getMethod().getModifiers();
			if ( unit1MethodModifier == unit2MethodModifier) {
				int timeStampComparisonResult = unit1.timeStamp.compareTo( unit2.timeStamp);
				if ( timeStampComparisonResult != 0) return timeStampComparisonResult;
			}
		
		boolean isUnit1MethodSynchronized = Modifier.isSynchronized( unit1MethodModifier);
		boolean isUnit2MethodSynchronized = Modifier.isSynchronized( unit2MethodModifier);
			if ( isUnit1MethodSynchronized && !isUnit2MethodSynchronized) return -1;
			if ( !isUnit1MethodSynchronized && isUnit2MethodSynchronized) return 1;
		
		boolean isUnit1MethodStatic = Modifier.isStatic( unit1MethodModifier);
		boolean isUnit2MethodStatic = Modifier.isStatic( unit2MethodModifier);
			if ( isUnit1MethodStatic && !isUnit2MethodStatic) return -1;
			if ( !isUnit1MethodStatic && isUnit2MethodStatic) return 1;
		
		boolean isUnit1MethodPublic = Modifier.isPublic( unit1MethodModifier);
		boolean isUnit2MethodPublic = Modifier.isPublic( unit2MethodModifier);
			if ( isUnit1MethodPublic && !isUnit2MethodPublic) return -1;
			if ( !isUnit1MethodPublic && isUnit2MethodPublic) return 1;
			if ( isUnit1MethodPublic && isUnit2MethodPublic) {
				int postModifierComparisonResult 
				= postModifierComparison( unit1, unit1MethodModifier, unit2, unit2MethodModifier);
				return postModifierComparisonResult;
			}
		
		boolean isUnit1MethodProtected = Modifier.isProtected( unit1MethodModifier);
		boolean isUnit2MethodProtected = Modifier.isProtected( unit2MethodModifier);
			if ( isUnit1MethodProtected && !isUnit2MethodProtected) return -1;
			if ( !isUnit1MethodProtected && isUnit2MethodProtected) return 1;
			if ( isUnit1MethodProtected && isUnit2MethodProtected) {
				int postModifierComparisonResult 
				= postModifierComparison( unit1, unit1MethodModifier, unit2, unit2MethodModifier);
				return postModifierComparisonResult;
			}
			
		boolean isUnit1MethodPrivate = Modifier.isPrivate( unit1MethodModifier);
		boolean isUnit2MethodPrivate = Modifier.isPrivate( unit2MethodModifier);
			if ( isUnit1MethodPrivate && !isUnit2MethodPrivate) return 1;
			if ( !isUnit1MethodPrivate && isUnit2MethodPrivate) return -1;
		
		int postModifierComparisonResult 
		= postModifierComparison( unit1, unit1MethodModifier, unit2, unit2MethodModifier);
			if ( logger.isDebugEnabled() && ( postModifierComparisonResult == 0)) {
				logger.debug( 
						String.format(
								"Comparison came out as identical for " 
								+ "next 2 SubscriberMethodUnit objects:" 
								+ "%n%1$c%2$s" + "%n%1$c%3$s",
								'\t',
								unit1.toString(), 
								unit2.toString())
						);
			}
		return postModifierComparisonResult;
	}
}
